package core.java;

import java.lang.reflect.*;
import java.util.Arrays;

/**
 * @apiNote  代理
 * 参考 https://blog.csdn.net/oworkn/article/details/52200736
 * 没看懂 原理 https://rejoy.iteye.com/blog/1627405
 * 代理无法代理重写object的方法...
 * 代理中包含该方法的最靠前的接口(不管是直接定义的还是从父接口中继承的)的方法对象会被传递给InvocationHandler对象的invoke方法，忽略已经传进来的方法调用的参数类型。
 */

interface Exinterface{
   void execu();
}

class Exinterfacelmpl implements Exinterface{

    @Override
    public void execu() {
        int a=1/0;
        System.out.println("Exinterfacelmpl");
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}

public class JdkProxy  implements InvocationHandler {
    private Exinterfacelmpl target;
    JdkProxy(Exinterfacelmpl target){
        this.target=target;
    }
    /**
     *
     */
    public Exinterface createProxy(){
        Class proxy = Proxy.getProxyClass(target.getClass().getClassLoader(),target.getClass().getInterfaces());
        try {
            Constructor constructor = proxy.getConstructor(new Class[]{InvocationHandler.class});
            return (Exinterface) constructor.newInstance(new Object[]{this});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return (Exinterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    /***
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     * 代理无法代理重写object的方法...
     * 为什么要有proxy  https://blog.csdn.net/rock154/article/details/80059344
     *
     *    invoke 第二个参数是method 怎么来的？$Proxy 动态实例 (即第一个参数proxy) 生成后(是class 文件)，通过反向编译，
     *    可以看到里面有如下一段静态代码块(来源：http://rejoy.iteye.com/blog/1627405)，
     *    也就是说只有proxy 实例在InvocationHandler 实现类里加载才能产生第二个参数method (静态代码块是虚拟机加载类的时候执行的，而且只执行一次)，
     *    所以$Proxy 实例要把自己传给InvocationHandler 的invoke 方法。
     *
     *      m3 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);
     *     InvocationHandler 源码中对proxy 的注释：the proxy instance that the method was invoked on
     *     method: 真实对象要实现的业务方法，由$Proxy 实例的静态代码块得到。
     *     args: 第二个参数method 的参数 。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("execu")){
            System.out.println("start ....");
            try {
                try {
                    method.invoke(target,null);
                }finally {
                    System.out.println(".... end");
                }
                System.out.println("success");
            }catch (Throwable e){
                System.out.println("Throwable");
            }
        }
        return null;
    }
    public static void main(String[] args) throws NoSuchMethodException {
         Exinterfacelmpl exinterfacelmpl=new Exinterfacelmpl();
         Exinterface exinterface=new JdkProxy(exinterfacelmpl).createProxy();
         exinterface.execu();
         System.out.println(exinterface.toString());
    }
}
